home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Linux Cubed Series 2: Applications
/
Linux Cubed Series 2 - Applications.iso
/
circuits
/
irsim-ca.2
/
irsim-ca
/
irsim-cap-9.2
/
src
/
irsim
/
newrstep.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-01-15
|
41KB
|
1,615 lines
/*
* *********************************************************************
* * Copyright (C) 1988, 1990 Stanford University. *
* * Permission to use, copy, modify, and distribute this *
* * software and its documentation for any purpose and without *
* * fee is hereby granted, provided that the above copyright *
* * notice appear in all copies. Stanford University *
* * makes no representations about the suitability of this *
* * software for any purpose. It is provided "as is" without *
* * express or implied warranty. Export of this software outside *
* * of the United States of America may require an export license. *
* *********************************************************************
*/
/*
* Event-driven timing simulation step for irsim.
*
* Use diamond shape region in R-V plane for dc voltage computation.
* Use 2-pole 1-zero model for pure charge sharing delay calculations.
* Use 2-pole zero-at-origin model for driven spike analysis.
* Details of the models can be found in Chorng-Yeong Chu's thesis:
* "Improved Models for Switch-Level Simulation" also available as a
* Stanford Technical Report CSL-TR-88-368.
*/
#include <stdio.h>
#include <math.h>
#include "defs.h"
#include "net.h"
#include "globals.h"
#ifdef DO_CACHE
#include "cache.h"
#endif
#define SMALL 1E-15 /* A small number */
#define LARGE 1E15 /* A large number */
#define LIMIT 1E8 /* R > LIMIT are considered infinite */
#define NP_RATIO 0.7 /* nmos-pmos ratio for spike analysis */
#define MIN( a, b ) ((a < b) ? a : b)
#define MAX( a, b ) ((a > b) ? a : b)
/* combine 2 resistors in parallel */
#define COMBINE( R1, R2 ) ( (R1) * (R2) / ( (R1) + (R2) ) )
/* combine 2 resistors in parallel, watch out for zero resistance */
#define COMBINE_R( A, B ) ( ((A) + (B) <= SMALL) ? 0 : COMBINE( A, B ) )
#define IsWatched( N ) ( (N)->nflags & WATCHED )
#define SetDebug( FL, N ) \
( ((debug & (FL)) == (FL) and IsWatched( N )) ? 1 : 0 )
/* flags used in thevenin structure */
#define T_DEFINITE 0x01 /* set for a definite rooted path */
#define T_UDELAY 0x02 /* set if user specified delay encountered */
#define T_SPIKE 0x04 /* set if charge-sharing spike possible */
#define T_DRIVEN 0x08 /* set if this branch is driven */
#define T_REFNODE 0x10 /* set for the reference node in pure c-s */
#define T_XTRAN 0x20 /* set if connecting through an X trans. */
#define T_INT 0x40 /* set if we should consider input slope */
#define T_DOMDRIVEN 0x80 /* set if branch driven to dominant voltage */
public int tunitdelay = 0; /* if <> 0, all transitions take
* 'tunitdelay' DELAY-units */
public int tdecay = 0; /* if <> 0, undriven nodes decay to
* X after 'tdecay' DELAY-units */
public char withdriven; /* TRUE if stage is driven by
* some input */
typedef struct
{
double min;
double max;
} Range;
/* Parameters gathered during a tree walk */
/* resists are in ohms, caps in pf */
typedef struct thevenin
{
union
{
Thev t;
nptr n;
} link; /* links these structures together */
int flags; /* flags defined above */
Range Clow; /* capacitance charged low */
Range Chigh; /* capacitance charged high */
Range Rup; /* resistance pulling up to Vdd */
Range Rdown; /* resistance pulling down to GND */
Range Req; /* resist. of present (parallel) xtor(s) */
Range V; /* normalized voltage range (0-1) */
double Rmin; /* minimum resistance to any driver */
double Rdom; /* minimum resistance to dominant driver */
double Rmax; /* maximum resistance to dominant driver */
double Ca; /* Adjusted non-switching capacitance */
double Cd; /* Adjusted total capacitance */
double tauD; /* Elmore delay (psec) */
double tauA; /* 1st order time-constant (psec) */
double tauP; /* 2nd order time-constant (psec) */
double Tin; /* input transition = (input_tau) * Rin */
short tplh; /* user specified low->high delay (DELTA) */
short tphl; /* user specified high->low delay (DELTA) */
char final; /* steady-state value calculated (H, L, X) */
char tau_done; /* if tau calculated, == dominant voltage */
char taup_done; /* if tauP calculated, == dominant voltage */
} thevenin;
typedef struct /* one for each possible dominant voltage */
{
nptr nd; /* list of nodes driven to this potential */
int spike; /* TRUE if this pot needs spike analysis */
} Dominant;
typedef struct
{
double ch_delay; /* charging delay */
double dr_delay; /* driven delay */
float peak; /* spike peak */
int charge; /* spike charge */
} SpikeRec, *pspk;
private Thev thev_free = NULL; /* Free list of thev structures */
private int inc_level; /* 1 if debug and node is watched */
private Dominant dom_pot[ N_POTS ]; /* dominant voltage structure */
private thevenin init_thev; /* pre-initialized thevenin structs */
private thevenin input_thev[ N_POTS ];
/* forward references */
private void scheduleDriven(), schedulePureCS(), UndoConnList();
private void parallel_op(), CleanEvents(), EnqueDecay();
private int ComputeDC();
private Thev get_dc_val(), series_op(), get_tau();
private double get_tauP();
private pspk ComputeSpike();
private void print_dc(), print_fval(), print_tau(), print_taup();
private void print_final(), print_spike(), print_spk();
public void linear_model( n )
nptr n;
{
int i, changes;
nevals++;
for( i = LOW; i <= HIGH; i++ )
dom_pot[i].nd = NULL, dom_pot[i].spike = FALSE;
if( n->nflags & VISITED )
BuildConnList( n );
changes = ComputeDC( n );
if( not changes )
CleanEvents( n );
else if( withdriven )
scheduleDriven();
else
schedulePureCS( n );
if( tdecay != 0 and not withdriven )
EnqueDecay( n );
UndoConnList( n );
}
private void CleanEvents( n )
register nptr n;
{
register evptr ev;
do
{
while( (ev = n->events) != NULL )
PuntEvent( n, ev );
}
while( (n = n->nlink) != NULL );
}
private void EnqueDecay( n )
register nptr n;
{
register evptr ev;
do
{
ev = n->events;
if( (ev == NULL) ? n->npot : ev->eval != X )
{
if( (debug & DEBUG_EV) and IsWatched( n ) )
lprintf( stdout, " decay transition for %s @ %.1fns\n",
pnode( n ), d2ns( cur_delta + tdecay ) );
enqueue_event( n, DECAY, (long) tdecay, (long) tdecay );
}
n = n->nlink;
}
while( n != NULL );
}
private void UndoConnList( n )
register nptr n;
{
register nptr next;
register lptr l;
register tptr t;
#ifdef CL_STATS
register int num_trans = 0;
for( next = n; next != NULL; next = next->nlink )
{
for( l = next->nterm; l != NULL; l = l->next )
{
t = l->xtor;
if( t->state != OFF )
t->tflags |= CROSSED;
}
}
#endif CL_STATS
do
{
next = n->nlink;
n->nlink = NULL;
n->n.thev->link.t = thev_free;
thev_free = n->n.thev;
for( l = n->nterm; l != NULL; l = l->next )
{
t = l->xtor;
if( t->state == OFF )
continue;
#ifdef CL_STATS
if( t->tflags & CROSSED )
num_trans ++;
#endif CL_STATS
if( not( t->tflags & (PBROKEN | BROKEN) ) )
{
register Thev r;
if( (r = t->scache.r) != NULL )
{
r->link.t = thev_free;
thev_free = r;
}
if( (r = t->dcache.r) != NULL )
{
r->link.t = thev_free;
thev_free = r;
}
}
t->scache.r = t->dcache.r = NULL;
t->tflags &= ~(CROSSED | BROKEN | PBROKEN | PARALLEL);
}
}
while( (n = next) != NULL );
#ifdef CL_STATS
RecordConnList( num_trans );
#endif CL_STATS
}
/*
* Schedule the final value for node 'nd'. Check to see if this final value
* invalidates other events. Since this event has more recent information
* regarding the state of the network, delete transitions scheduled to come
* after it. Zero-delay transitions are avoided by turning them into unit
* delays (1 delta). Events scheduled to occur at the same time as this event
* and driving the node to the same value are not punted.
* Finally, before scheduling the final value, check that it is different
* from the previously calculated value for the node.
*/
private void QueueFVal( nd, fval, tau, delay )
nptr nd;
int fval;
double tau, delay;
{
register evptr ev;
register Ulong delta;
int queued = 0;
delta = cur_delta + (Ulong) ps2d( delay );
if( delta == cur_delta ) /* avoid zero delay */
delta++;
while( (ev = nd->events) != NULL and ev->ntime >= delta )
{
if( ev->ntime == delta and ev->eval == fval )
break;
PuntEvent( nd, ev );
}
delta -= cur_delta;
if( fval != ((ev == NULL) ? nd->npot : ev->eval) )
{
enqueue_event( nd, fval, (long) delta, (long) ps2d( tau ) );
queued = 1;
}
if( (debug & DEBUG_EV) and IsWatched( nd ) )
print_final( nd, queued, tau, delta );
}
private void QueueSpike( nd, spk )
nptr nd;
pspk spk;
{
register evptr ev;
register Ulong ch_delta, dr_delta;
while( (ev = nd->events) != NULL )
PuntEvent( nd, ev );
if( spk == NULL ) /* no spike, just punt events */
{
return;
}
ch_delta = (Ulong) ps2d( spk->ch_delay );
dr_delta = (Ulong) ps2d( spk->dr_delay );
if( ch_delta == 0 )
ch_delta = 1;
if( dr_delta == 0 )
dr_delta = 1;
if( (debug & DEBUG_EV) and IsWatched( nd ) )
print_spike( nd, spk, ch_delta, dr_delta );
if( dr_delta <= ch_delta ) /* no zero delay spikes, done */
{
return;
}
/* enqueue spike and final value events */
enqueue_event( nd, (int) spk->charge, (long) ch_delta, (long) ch_delta );
enqueue_event( nd, (int) nd->npot, (long) dr_delta, (long) ch_delta );
}
private void scheduleDriven()
{
register nptr nd;
register Thev r;
int dom;
double tau, delay;
for( dom = 0; dom < N_POTS; dom++ )
{
for( nd = dom_pot[ dom ].nd; nd != NULL; nd = r->link.n )
{
inc_level = SetDebug( DEBUG_TAU | DEBUG_TW, nd );
r = get_tau( nd, (tptr) NULL, dom, inc_level );
if( inc_level == 0 and SetDebug( DEBUG_TAU, nd ) )
print_tau( nd, r, -1 );
r->tauA = r->Rdom * r->Ca;
r->tauD = r->Rdom * r->Cd;
if( r->flags & T_SPIKE ) /* deal with these later */
continue;
if( nd->npot == r->final ) /* no change, just punt */
{
evptr ev;
while( (ev = nd->events) != NULL )
PuntEvent( nd, ev );
continue;
}
else if( tunitdelay )
{
delay = tunitdelay;
tau = 0.0;
}
else if( r->flags & T_UDELAY )
{
switch( r->final )
{
case LOW : tau = d2ps( r->tphl ); break;
case HIGH : tau = d2ps( r->tplh ); break;
case X : tau = d2ps( MIN( r->tphl, r->tplh ) ); break;
}
delay = tau;
}
else
{
if( r->final == X )
tau = r->Rmin * r->Ca;
else if( r->flags & T_DEFINITE )
tau = r->Rmax * r->Ca;
else
tau = r->Rdom * r->Ca;
if( (r->flags & T_INT) and r->Tin > 0.5 )
delay = sqrt( tau * tau + d2ps( r->Tin ) * r->Ca );
else
delay = tau;
}
QueueFVal( nd, (int) r->final, tau, delay );
}
if( dom_pot[ dom ].spike )
{
pspk spk;
for( nd = dom_pot[ dom ].nd; nd != NULL; nd = nd->n.thev->link.n )
{
r = nd->n.thev;
if( not (r->flags & T_SPIKE) )
continue;
inc_level = SetDebug( DEBUG_TAUP | DEBUG_TW, nd );
r->tauP = get_tauP( nd, (tptr) NULL, dom, inc_level );
r->tauP *= r->Rdom / r->tauA;
QueueSpike( nd, ComputeSpike( nd, r, dom ) );
}
}
}
}
private void schedulePureCS( nlist )
nptr nlist;
{
register nptr nd;
register Thev r;
int dom;
double taup, tau, delay;
r = nlist->n.thev;
dom = r->final;
r->flags |= T_REFNODE;
taup = 0.0;
for( nd = nlist; nd != NULL; nd = nd->nlink )
{
inc_level = SetDebug( DEBUG_TAU | DEBUG_TW, nd );
r = get_tau( nd, (tptr) NULL, dom, inc_level );
r->tauD = r->Rdom * r->Ca;
switch( dom )
{
case LOW :
r->tauA = r->Rdom * ( r->Ca - r->Cd * r->V.max );
break;
case HIGH :
r->tauA = r->Rdom * ( r->Cd * ( 1 - r->V.min ) - r->Ca );
break;
case X : /* approximate Vf = 0.5 */
r->tauA = r->Rdom * ( r->Ca - r->Cd * 0.5 );
break;
}
taup += r->tauA * nd->ncap;
}
r = nlist->n.thev;
taup = taup / (r->Clow.min + r->Chigh.max); /* tauP = tauP / CT */
for( nd = nlist; nd != NULL; nd = nd->nlink )
{
r = nd->n.thev;
if( r->final == nd->npot ) /* no change, no delay */
delay = tau = 0.0;
else
{
switch( r->final )
{
case LOW : tau = (r->tauA - taup) / (1.0 - r->V.max); break;
case HIGH : tau = (taup - r->tauA) / r->V.min; break;
case X : tau = (r->tauA - taup) * 2.0; break;
}
if( tau < 0.0 )
tau = 0.0;
if( tunitdelay )
delay = tunitdelay, tau = 0.0;
else
delay = tau;
}
QueueFVal( nd, (int) r->final, tau, delay );
}
}
/*
* Compute the final value for each node on the connection list.
* This routine will update V.min and V.max and add the node to the
* corresponding dom_driver entry. Return TRUE if any node changes value.
*/
private int ComputeDC( nlist )
nptr nlist;
{
register nptr this, next;
register Thev r;
int anyChange = FALSE;
for( this = nlist; this != NULL; this = this->nlink )
{
inc_level = SetDebug( DEBUG_DC | DEBUG_TW, this );
this->n.thev = r = get_dc_val( this, (tptr) NULL, inc_level );
if( withdriven )
{
if( r->Rdown.min >= LIMIT )
r->V.min = 1;
else
r->V.min = r->Rdown.min / ( r->Rdown.min + r->Rup.max );
if( r->Rup.min >= LIMIT )
r->V.max = 0;
else
r->V.max = r->Rdown.max / ( r->Rdown.max + r->Rup.min );
}
else /* use charge/total charge if undriven */
{
r->V.min = r->Chigh.min / ( r->Chigh.min + r->Clow.max );
r->V.max = r->Chigh.max / ( r->Chigh.max + r->Clow.min );
}
if( r->V.min >= this->vhigh )
r->final = HIGH;
else if( r->V.max <= this->vlow )
r->final = LOW;
else
r->final = X;
if( withdriven )
{
/*
* if driven and indefinite, driven value must equal
* charging value otherwise the final value is X
*/
if( r->final != X and not (r->flags & T_DEFINITE) )
{
char cs_val;
if( r->Chigh.min >= this->vhigh * (r->Chigh.min + r->Clow.max) )
cs_val = HIGH;
else if( r->Chigh.max <= this->vlow * (r->Chigh.max + r->Clow.min) )
cs_val = LOW;
else
cs_val = X; /* always X */
if( cs_val != r->final )
r->final = X;
}
r->link.n = dom_pot[ r->final ].nd; /* add it to list */
dom_pot[ r->final ].nd = this;
/* possible spike if no transition and opposite charge exists */
if( r->final == this->npot and (
(r->final == LOW and r->Chigh.min > SMALL) or
(r->final == HIGH and r->Clow.min > SMALL) ) )
{
r->flags |= T_SPIKE;
dom_pot[ r->final ].spike = TRUE;
anyChange = TRUE;
}
}
if( r->final != this->npot )
anyChange = TRUE;
if( SetDebug( DEBUG_DC, this ) )
print_fval( this, r );
}
return( anyChange );
}
#define NEW_THEV( T ) \
{ \
if( ((T) = thev_free) == NULL ) \
(T) = (Thev) MallocList( sizeof( thevenin ), 1 ); \
thev_free = T->link.t; \
}
/*
* Compute the parametes used to calculate the final value (Chigh, Clow,
* Rup, Rdown) by doing a depth-first traversal of the tree rooted at node
* 'n'. The traversal is done by a recursive walk through the tree. Note that
* the stage is already a simple tree; loops are broken by BuildConnList. As
* a side effect also compute Req of the present transistor and all other
* transistors in parallel with it, and any specified user delays.
* The parameters are:
*
* n : the node whose dc parameters we want.
* tran : the transistor that leads to 'n' (NULL if none).
* level : level of recursion if we are debugging this node, else 0.
*/
private Thev get_dc_val( n, tran, level )
nptr n;
tptr tran;
int level;
{
register lptr l;
register tptr t;
register nptr other;
register Thev r;
Thev cache, *pcache;
NEW_THEV( r );
if( n->nflags & INPUT )
{
*r = input_thev[ n->npot ];
return( r );
}
*r = init_thev;
switch( n->npot )
{
case LOW : r->Clow.min = r->Clow.max = n->ncap; break;
case X : r->Clow.max = r->Chigh.max = n->ncap; break;
case HIGH : r->Chigh.min = r->Chigh.max = n->ncap; break;
}
for( l = n->nterm; l != NULL; l = l->next )
{
t = l->xtor;
/* ignore path going back or through a broken loop */
if( t == tran or t->state == OFF or (t->tflags & (BROKEN | PBROKEN)) )
continue;
if( n == t->source )
{
other = t->drain; pcache = &(t->dcache.r);
}
else
{
other = t->source; pcache = &(t->scache.r);
}
/*
* if cache is not empty use the value found there, otherwise
* compute what is on the other side of the transistor and
* transmit the result through a series operation.
*/
if( (cache = *pcache) == NULL )
{
cache = series_op( get_dc_val( other, t, level+inc_level ), t );
*pcache = cache;
}
parallel_op( r, cache );
}
if( n->nflags & USERDELAY ) /* record user delays, if any */
{
r->tplh = n->tplh; r->tphl = n->tphl;
r->flags |= T_UDELAY;
}
if( level != 0 )
print_dc( n, r, level );
return( r );
}
/*
* The following macros set Req to the appropriate dynamic resistance.
* If the transistor state is UNKNOWN then also set the T_XTRAN flag.
*/
#define GetReq( R, T, TYPE ) \
{ \
if( ( (T)->tflags & PARALLEL ) ) \
get_parallel( R, T, TYPE ); \
else \
{ \
(R)->Req.min = (T)->r->dynres[TYPE]; \
if( (T)->state == UNKNOWN ) \
(R)->flags |= T_XTRAN; \
else \
(R)->Req.max = (T)->r->dynres[TYPE];\
} \
} \
#define GetMinR( R, T ) \
{ \
if( ( (T)->tflags & PARALLEL ) ) \
get_min_parallel( R, T ); \
else \
{ \
(R)->Req.min = MIN( (T)->r->dynhigh, (T)->r->dynlow ); \
if( (T)->state == UNKNOWN ) \
(R)->flags |= T_XTRAN; \
else \
(R)->Req.max = (R)->Req.min; \
} \
} \
/*
* Do the same as GetReq but deal with parallel transistors.
*/
private void get_parallel( r, t, restype )
Thev r;
register tptr t;
int restype;
{
register Resists *rp = t->r;
double gmin, gmax;
gmin = 1.0 / rp->dynres[ restype ];
gmax = (t->state == UNKNOWN ) ? 0.0 : gmin;
for( t = par_list( t ); t != NULL; t = t->dcache.t )
{
rp = t->r;
gmin += 1.0 / rp->dynres[ restype ];
if( t->state != UNKNOWN )
gmax += 1.0 / rp->dynres[ restype ];
}
r->Req.min = 1.0 / gmin;
if( gmax == 0.0 )
r->flags |= T_XTRAN;
else
r->Req.max = 1.0 / gmax;
}
/*
* Do the same as get_parallel but use the minimum dynamic resistance.
*/
get_min_parallel( r, t )
Thev r;
register tptr t;
{
register Resists *rp = t->r;
double gmin, gmax, tmp;
gmin = 1.0 / MIN( rp->dynlow, rp->dynhigh );
gmax = (t->state == UNKNOWN ) ? 0.0 : gmin;
for( t = par_list( t ); t != NULL; t = t->dcache.t )
{
rp = t->r;
tmp = 1.0 / MIN( rp->dynlow, rp->dynhigh );
gmin += tmp;
if( t->state != UNKNOWN )
gmax += tmp;
}
r->Req.min = 1.0 / gmin;
if( gmax == 0.0 )
r->flags |= T_XTRAN;
else
r->Req.max = 1.0 / gmax;
}
/*
* Add transistor 't' in series with thevenin struct 'r'. As a side effect
* set Req for 't'. The midpoint voltage is used to determine whether to
* use the dynamic-high or dynamic-low resistance. If the branch connecting
* to 't' is not driven by an input then use the charge information. The
* current estimates of both resistance or capacitance is used.
*/
private Thev series_op( r, t )
register Thev r;
register tptr t;
{
double up_min, down_min;
if( not (r->flags & T_DRIVEN) )
{
if( r->Chigh.min > r->Clow.max )
GetReq( r, t, R_HIGH )
else if( r->Chigh.max < r->Clow.min )
GetReq( r, t, R_LOW )
else
GetMinR( r, t )
return( r ); /* no driver, so just set Req */
}
if( r->Rdown.min > r->Rup.max )
GetReq( r, t, R_HIGH )
else if( r->Rdown.max < r->Rup.min )
GetReq( r, t, R_LOW )
else
GetMinR( r, t )
up_min = r->Rup.min;
down_min = r->Rdown.min;
if( up_min < LIMIT )
r->Rup.min += r->Req.min * ( 1.0 + up_min / r->Rdown.max );
if( down_min < LIMIT )
r->Rdown.min += r->Req.min * ( 1.0 + down_min / r->Rup.max );
if( r->flags & T_XTRAN )
{
r->flags &= ~T_DEFINITE;
r->Rup.max = r->Rdown.max = LARGE;
}
else
{
if( r->Rup.max < LIMIT )
r->Rup.max += r->Req.max * ( 1.0 + r->Rup.max / down_min );
if( r->Rdown.max < LIMIT )
r->Rdown.max += r->Req.max * ( 1.0 + r->Rdown.max / up_min );
}
return( r );
}
/* make oldr = (oldr || newr), but watch out for infinte resistances. */
#define DoParallel( oldr, newr ) \
{ \
if( oldr > LIMIT ) \
oldr = newr; \
else if( newr < LIMIT ) \
oldr = COMBINE( oldr, newr ); \
} \
/*
* Combine the new resistance block of the tree walk with the current one.
* Accumulate the low and high capacitances, the user-specified
* delay (if any), and the driven flag of the resulting structure.
*/
private void parallel_op( r, new )
Thev r, new;
{
r->Clow.max += new->Clow.max;
r->Chigh.max += new->Chigh.max;
if( not (new->flags & T_XTRAN) )
{
r->Clow.min += new->Clow.min;
r->Chigh.min += new->Chigh.min;
}
/*
* Accumulate the minimum user-specified delay only if the new block
* has some drive associated with it.
*/
if( (new->flags & (T_DEFINITE | T_UDELAY)) == (T_DEFINITE | T_UDELAY) )
{
if( r->flags & T_UDELAY )
{
r->tplh = MIN( r->tplh, new->tplh );
r->tphl = MIN( r->tphl, new->tphl );
}
else
{
r->tplh = new->tplh;
r->tphl = new->tphl;
r->flags |= T_UDELAY;
}
}
if( not (new->flags & T_DRIVEN) )
return; /* undriven, just update caps */
r->flags |= T_DRIVEN; /* combined result is driven */
DoParallel( r->Rup.min, new->Rup.min );
DoParallel( r->Rdown.min, new->Rdown.min );
if( r->flags & new->flags & T_DEFINITE ) /* both definite blocks */
{
DoParallel( r->Rup.max, new->Rup.max );
DoParallel( r->Rdown.max, new->Rdown.max );
}
else if( new->flags & T_DEFINITE ) /* only new is definite */
{
r->Rup.max = new->Rup.max;
r->Rdown.max = new->Rdown.max;
r->flags |= T_DEFINITE; /* result is definite */
}
else /* new (perhaps r) is indefinite */
{
if( new->Rup.max < r->Rup.max ) r->Rup.max = new->Rup.max;
if( new->Rdown.max < r->Rdown.max ) r->Rdown.max = new->Rdown.max;
}
}
/*
* Determine the input time-constant (input-slope * rstatic). We are only
* interseted in transistors that just turned on (its gate has a transition
* at time == cur_delta). We must be careful not to report as a transition
* nodes that stop being inputs (hist->delay == 0 and hist->inp == 0).
*/
#define IsCurrTransition( H ) \
( (H)->time == cur_delta and ((H)->inp == 1 or (H)->t.r.delay != 0) )
/*
* Return TRUE if we should consider the input slope of this transistor. As
* a side-effect, return the input time constant in 'ptin'.
*/
private int GetTin( t, ptin )
register tptr t;
double *ptin;
{
register hptr h;
int is_int = FALSE;
if( t->state != ON )
return( FALSE );
if( (t->ttype & GATELIST) == 0 )
{
h = t->gate->curr;
if( IsCurrTransition( h ) )
{
*ptin = h->t.r.rtime * t->r->rstatic;
is_int = TRUE;
}
}
else
{
double tmp = 0.0;
for( t = (tptr) t->gate; t != NULL; t = t->scache.t )
{
h = t->gate->curr;
if( IsCurrTransition( h ) )
{
is_int = TRUE;
tmp += h->t.r.rtime * t->r->rstatic;
}
}
*ptin = tmp;
}
return( is_int );
}
#define InputTau( T, PR ) \
( ((T)->tflags & PARALLEL) ? parallel_GetTin( T, PR ) : GetTin( T, PR ) )
private int parallel_GetTin( t, itau )
register tptr t;
double *itau;
{
double tin, tmp = 0.0;
int is_int;
is_int = GetTin( t, &tin );
for( t = par_list( t ); t != NULL; t = t->dcache.t )
{
if( GetTin( t, &tmp ) )
{
tin = (is_int) ? COMBINE_R( tin, tmp ) : tmp;
is_int = TRUE;
}
*itau = tin;
}
return( is_int );
}
/*
* Compute the parameters needed to calculate the 1st order time-constants
* (Rmin, Rdom, Rmax, Ca, Cd, Tin) by doing a depth-first traversal of the
* tree rooted at node 'n'. The parameters are gathered by performing a
* recursive tree walk similar to ComputeDC. As a side effect, the tauP
* field will contain the multiplication factor to move a capacitor across
* a transistor using 'current distribution', this field may be required
* later when computing tauP. The parameters are:
*
* n : the node whose time-constant parameters we want.
* dom : the value of the dominant driver for this stage.
* tran : the transistor that leads to 'n' (NULL if none).
* level : level of recursion if we are debugging this node, else 0.
*
* This routine can be called more than once if the stage is dominated by
* more than 1 potential, hence the tau_done flag keeps track of the potential
* for which the parameters stored in the cache were computed. If the flag
* value and the current dominant potential do not match, we go ahead and
* recompute the values.
*/
private Thev get_tau( n, tran, dom, level )
nptr n;
tptr tran;
int dom;
int level;
{
register Thev r, cache;
register lptr l;
register tptr t;
register nptr other;
if( tran == NULL )
r = n->n.thev;
else
r = (tran->source == n) ? tran->scache.r : tran->dcache.r;
r->tau_done = dom;
if( n->nflags & INPUT )
{
r->Tin = r->Rmin = r->Ca = r->Cd = 0.0;
if( n->npot == dom )
{
r->Rdom = r->Rmax = 0.0;
r->flags |= T_DOMDRIVEN;
}
else
{
r->flags &= ~(T_DOMDRIVEN | T_INT);
if( dom == X )
r->Rdom = r->Rmax = 0.0;
else
r->Rdom = r->Rmax = LARGE;
}
return( r );
}
if( n->n.thev->flags & T_REFNODE ) /* reference node in pure CS */
{
r->Rmin = r->Rdom = r->Rmax = 0.0;
r->Ca = r->Cd = 0.0;
return( r );
}
r->Rmin = r->Rdom = r->Rmax = LARGE;
r->Cd = n->ncap;
if( dom == X ) /* assume X nodes are charged high */
r->Ca = (n->npot == LOW) ? 0.0 : n->ncap;
else
r->Ca = (n->npot == dom) ? 0.0 : n->ncap;
r->Tin = 0.0;
r->flags &= ~(T_DOMDRIVEN | T_INT);
for( l = n->nterm; l != NULL; l = l->next )
{
t = l->xtor;
if( t->state == OFF or t == tran or (t->tflags & (BROKEN | PBROKEN)) )
continue;
if( n == t->source )
{
other = t->drain; cache = t->dcache.r;
}
else
{
other = t->source; cache = t->scache.r;
}
if( cache->tau_done != dom )
{
double oldr;
cache = get_tau( other, t, dom, level + inc_level );
/* Only use input slope for xtors on the dominant (driven) path */
if( (cache->flags & T_DOMDRIVEN) and InputTau( t, &oldr ) )
{
cache->flags |= T_INT;
cache->Tin += oldr;
}
oldr = cache->Rdom;
cache->Rmin += cache->Req.min;
cache->Rdom += cache->Req.min;
if( cache->flags & T_XTRAN )
cache->Rmax = LARGE;
else
cache->Rmax += cache->Req.max;
/* Exclude capacitors if the other side of X transistor == dom */
if( (cache->flags & T_XTRAN) and other->npot == dom )
cache->tauP = cache->Ca = cache->Cd = 0.0;
else if( oldr > LIMIT )
cache->tauP = 1.0;
else
{
cache->tauP = oldr / cache->Rdom;
cache->Ca *= cache->tauP;
cache->Cd *= cache->tauP;
}
}
r->Ca += cache->Ca;
r->Cd += cache->Cd;
r->Rmin = COMBINE( r->Rmin, cache->Rmin );
if( r->Rdom > LIMIT )
{
r->Rdom = cache->Rdom;
r->Rmax = cache->Rmax;
}
else if( cache->Rdom < LIMIT )
{
r->Rdom = COMBINE( r->Rdom, cache->Rdom );
r->Rmax = COMBINE( r->Rmax, cache->Rmax );
}
if( cache->flags & T_DOMDRIVEN )
r->flags |= T_DOMDRIVEN; /* at least 1 dominant driven path */
if( cache->flags & T_INT )
{
if( r->flags & T_INT )
r->Tin = COMBINE_R( r->Tin, cache->Tin );
else
{
r->Tin = cache->Tin;
r->flags |= T_INT;
}
}
}
if( level > 0 )
print_tau( n, r, level );
return( r );
}
/*
* Calculate the 2nd order time constant (tauP) for the net configuration
* as seen through node 'n'. The net traversal and the parameters are
* similar to 'get_tau'. Note that at this point we have not have calculated
* tauA for nodes not driven to the dominant potential, hence we need to
* compute those by first calling get_tau. This routine will update the tauP
* entry as well as the taup_done flag.
*/
private double get_tauP( n, tran, dom, level )
nptr n;
tptr tran;
int dom;
int level;
{
register lptr l;
register tptr t;
register Thev r;
nptr other;
double taup;
if( n->nflags & INPUT )
return( 0.0 );
r = n->n.thev;
if( r->tau_done != dom ) /* compute tauA for the node */
{
r = get_tau( n, (tptr) NULL, dom, 0 );
r->tauA = r->Rdom * r->Ca;
r->tauD = r->Rdom * r->Cd;
}
taup = r->tauA * n->ncap;
for( l = n->nterm; l != NULL; l = l->next )
{
t = l->xtor;
if( t->state == OFF or t == tran or (t->tflags & (BROKEN | PBROKEN)) )
continue;
if( t->source == n )
other = t->drain, r = t->dcache.r;
else
other = t->source, r = t->scache.r;
if( r->taup_done != dom )
{
r->tauP *= get_tauP( other, t, dom, level + inc_level );
r->taup_done = dom;
}
taup += r->tauP;
}
if( level > 0 )
print_taup( n, level, taup );
return( taup );
}
#include "spiketbl.c"
/*
* Compute the size of spike. If the spike is too small return NULL, else
* fill in the appropriate structure and return a pointer to it. In order
* to determine in which table to lookup the spike peak we look at the
* conductivity of all ON transistors connected to node 'nd'; the type with
* the largest conductivity determines whether it is mostly an nmos or pmos
* network. This simple scheme should work for most simple nets.
*/
private pspk ComputeSpike( nd, r, dom )
nptr nd;
register Thev r;
int dom;
{
int rtype, tab_indx, alpha, beta, N;
float nmos, pmos;
static SpikeRec spk;
register lptr l;
if( r->tauP <= SMALL ) /* no capacitance, no spike */
{
if( (debug & DEBUG_SPK) and IsWatched( nd ) )
lprintf( stdout, " spike( %s ) ignored (taup=0)\n" );
return( NULL );
}
rtype = (dom == LOW) ? R_LOW : R_HIGH;
nmos = pmos = 0;
for( l = nd->nterm; l != NULL; l = l->next )
{
register tptr t;
t = l->xtor;
if( t->state == OFF or (t->tflags & BROKEN) )
continue;
if( BASETYPE( t->ttype ) == PCHAN )
pmos += 1.0 / t->r->dynres[ rtype ];
else
nmos += 1.0 / t->r->dynres[ rtype ];
}
if( nmos > NP_RATIO * (pmos + nmos) ) /* mostly nmos */
tab_indx = (rtype == R_LOW) ? NLSPKMIN : NLSPKMAX;
else if( pmos > NP_RATIO * (pmos + nmos) ) /* mostly pmos */
tab_indx = (rtype == R_LOW) ? NLSPKMAX : NLSPKMIN;
else
tab_indx = LINEARSPK;
alpha = (int) ( SPIKETBLSIZE * r->tauA / (r->tauA + r->tauP - r->tauD) );
if( alpha < 0 )
alpha = 0;
else if( alpha > SPIKETBLSIZE )
alpha = SPIKETBLSIZE;
beta = (int) ( SPIKETBLSIZE * (r->tauD - r->tauA) / r->tauD );
if( beta < 0 )
beta = 0;
else if( beta > SPIKETBLSIZE )
beta = SPIKETBLSIZE;
spk.peak = spikeTable[ tab_indx ][ beta ][ alpha ];
spk.ch_delay = delayTable[ beta ][ alpha ];
if( dom == LOW )
{
if( spk.peak <= nd->vlow ) /* spike is too small */
goto no_spike;
else
spk.charge = (spk.peak >= nd->vhigh) ? HIGH : X;
}
else /* dom == HIGH */
{
if( spk.peak <= 1.0 - nd->vhigh )
goto no_spike;
else
spk.charge = (spk.peak >= 1.0 - nd->vlow) ? LOW : X;
}
spk.ch_delay *= r->tauA * r->tauD / r->tauP;
if( r->Rmax < LARGE )
spk.dr_delay = r->Rmax * r->Ca;
else
spk.dr_delay = r->Rdom * r->Ca;
if( (debug & DEBUG_SPK) and IsWatched( nd ) )
print_spk( nd, r, tab_indx, dom, alpha, beta, &spk, TRUE );
return( &spk );
no_spike :
if( (debug & DEBUG_SPK) and IsWatched( nd ) )
print_spk( nd, r, tab_indx, dom, alpha, beta, &spk, FALSE );
return( NULL );
}
/*
* Initialize pre-initialized thevenin structs. I want to get it right
* and this is much safer than letting the compiler initialize it.
*/
public void InitThevs()
{
register Thev t;
init_thev.link.n = NULL;
init_thev.flags = 0;
init_thev.Clow.min = 0.0;
init_thev.Clow.max = 0.0;
init_thev.Chigh.min = 0.0;
init_thev.Chigh.max = 0.0;
init_thev.Rup.min = LARGE;
init_thev.Rup.max = LARGE;
init_thev.Rdown.min = LARGE;
init_thev.Rdown.max = LARGE;
init_thev.Req.min = LARGE;
init_thev.Req.max = LARGE;
init_thev.V.min = 1.0;
init_thev.V.max = 0.0;
init_thev.Rmin = LARGE;
init_thev.Rdom = LARGE;
init_thev.Rmax = LARGE;
init_thev.Ca = 0.0;
init_thev.Cd = 0.0;
init_thev.tauD = 0.0;
init_thev.tauA = 0.0;
init_thev.tauP = 0.0;
init_thev.Tin = SMALL;
init_thev.tplh = 0;
init_thev.tphl = 0;
init_thev.final = X;
init_thev.tau_done = N_POTS;
init_thev.taup_done = N_POTS;
t = &input_thev[ LOW ];
t->link.n = NULL;
t->flags = T_DEFINITE | T_DRIVEN;
t->Clow.min = 0.0;
t->Clow.max = 0.0;
t->Chigh.min = 0.0;
t->Chigh.max = 0.0;
t->Rup.min = LARGE;
t->Rup.max = LARGE;
t->Rdown.min = SMALL;
t->Rdown.max = SMALL;
t->Req.min = LARGE;
t->Req.max = LARGE;
t->V.min = 0.0;
t->V.max = 0.0;
t->Rmin = SMALL;
t->Rdom = LARGE;
t->Rmax = LARGE;
t->Ca = 0.0;
t->Cd = 0.0;
t->tauD = 0.0;
t->tauA = 0.0;
t->tauP = 0.0;
t->Tin = SMALL;
t->tplh = 0;
t->tphl = 0;
t->final = LOW;
t->tau_done = N_POTS;
t->taup_done = N_POTS;
t = &input_thev[ HIGH ];
t->link.n = NULL;
t->flags = T_DEFINITE | T_DRIVEN;
t->Clow.min = 0.0;
t->Clow.max = 0.0;
t->Chigh.min = 0.0;
t->Chigh.max = 0.0;
t->Rup.min = SMALL;
t->Rup.max = SMALL;
t->Rdown.min = LARGE;
t->Rdown.max = LARGE;
t->Req.min = LARGE;
t->Req.max = LARGE;
t->V.min = 1.0;
t->V.max = 1.0;
t->Rmin = SMALL;
t->Rdom = LARGE;
t->Rmax = LARGE;
t->Ca = 0.0;
t->Cd = 0.0;
t->tauD = 0.0;
t->tauA = 0.0;
t->tauP = 0.0;
t->Tin = SMALL;
t->tplh = 0;
t->tphl = 0;
t->final = HIGH;
t->tau_done = N_POTS;
t->taup_done = N_POTS;
t = &input_thev[ X ];
t->link.n = NULL;
t->flags = T_DEFINITE | T_DRIVEN;
t->Clow.min = 0.0;
t->Clow.max = 0.0;
t->Chigh.min = 0.0;
t->Chigh.max = 0.0;
t->Rup.min = SMALL;
t->Rup.max = LARGE;
t->Rdown.min = SMALL;
t->Rdown.max = LARGE;
t->Req.min = LARGE;
t->Req.max = LARGE;
t->V.min = 1.0;
t->V.max = 0.0;
t->Rmin = SMALL;
t->Rdom = LARGE;
t->Rmax = LARGE;
t->Ca = 0.0;
t->Cd = 0.0;
t->tauD = 0.0;
t->tauA = 0.0;
t->tauP = 0.0;
t->Tin = SMALL;
t->tplh = 0;
t->tphl = 0;
t->final = X;
t->tau_done = N_POTS;
t->taup_done = N_POTS;
input_thev[ X+1 ] = input_thev[ X ];
}
/*
* printing routines for debug mode
*/
private char *get_indent( i )
int i;
{
static char indent[] = ".........................";
static char spaces[] = " ";
static int last_c = 0;
int c;
if( i >= sizeof( indent ) )
i = sizeof( indent ) - 1;
else
i++;
indent[ i ] = '\0';
lprintf( stdout, " %s", indent );
indent[ i ] = ' ';
spaces[ last_c ] = ' ';
last_c = i + 1;
spaces[ last_c ] = '\0';
return( spaces );
}
private char *r2ascii( s, r )
char *s;
double r;
{
if( r >= LIMIT )
(void) strcpy( s, " - " );
else if( r > 1.0 )
{
int exp;
for( exp = 0; r >= 1000.0; exp++, r *= 0.001 );
(void) sprintf( s, "%.1f%c", r, " KMG"[ exp ] );
}
else
(void) sprintf( s, "%g", r );
return( s );
}
private void print_dc( n, r, level )
nptr n;
Thev r;
int level;
{
char cbuf[4][20];
char *indent;
indent = get_indent( level );
lprintf( stdout, "compute_dc( %s )\n%s", pnode( n ), indent );
if( not withdriven )
lprintf( stdout, "pure cs:" );
else
lprintf( stdout, "%sefinite", (r->flags & T_DEFINITE) ? "D" : "Ind");
lprintf( stdout, " Rup=[%s, %s] Rdown=[%s, %s]\n",
r2ascii( cbuf[0], r->Rup.min ), r2ascii( cbuf[1], r->Rup.max ),
r2ascii( cbuf[2], r->Rdown.min ), r2ascii( cbuf[3], r->Rdown.max ) );
lprintf( stdout, "%sClow=[%.2f, %.2f] Chigh=[%.2f, %.2f]\n",
indent, r->Clow.min, r->Clow.max, r->Chigh.min, r->Chigh.max );
}
private void print_fval( n, r )
nptr n;
Thev r;
{
lprintf( stdout, " final_value( %s ) V=[%.2f, %.2f] => %c",
pnode( n ), r->V.min, r->V.max, vchars[ r->final ] );
lprintf( stdout, (r->flags & T_SPIKE) ? " (spk)\n" : "\n" );
}
private void print_tau( n, r, level )
nptr n;
Thev r;
int level;
{
char cbuf[3][20];
char *indent;
indent = get_indent( level );
lprintf( stdout, "compute_tau( %s )\n%s", pnode( n ), indent );
lprintf( stdout, "{Rmin=%s Rdom=%s Rmax=%s}",
r2ascii( cbuf[0], r->Rmin ), r2ascii( cbuf[1], r->Rdom ),
r2ascii( cbuf[2], r->Rmax ) );
lprintf( stdout, " {Ca=%.2f Cd=%.2f}\n", r->Ca, r->Cd );
lprintf( stdout, "%stauA=%.1f tauD=%.1f ns, RTin=",
indent, ps2ns( r->Rdom * r->Ca ), ps2ns( r->Rdom * r->Cd ) );
if( r->flags & T_INT )
lprintf( stdout, "%.1f ohm*ns\n", d2ns( r->Tin ) );
else
lprintf( stdout, "-\n" );
}
private void print_taup( n, level, taup )
nptr n;
int level;
double taup;
{
(void) get_indent( level );
lprintf( stdout, "tauP( %s ) = %.1f ns\n", pnode( n ), ps2ns( taup ) );
}
private void print_final( nd, queued, tau, delay )
nptr nd;
int queued;
double tau;
Ulong delay;
{
Thev r;
Ulong dtau; /* tau in deltas */
r = nd->n.thev;
dtau = ps2d( tau );
lprintf( stdout, " [event %s->%c @ %.1f] ",
pnode( cur_node ), vchars[ cur_node->npot ], d2ns( cur_delta ) );
lprintf( stdout, (queued ? "causes %stransition for" : "%sevaluates"),
(withdriven ? "" : "CS ") );
lprintf( stdout, " %s: %c -> %c", pnode( nd ),
vchars[ nd->npot ], vchars[ r->final ] );
lprintf( stdout, " (tau=%.1fns, delay=%.1fns)\n",
d2ns( dtau ), d2ns( delay ) );
}
private void print_spike( nd, spk, ch_delay, dr_delay )
nptr nd;
pspk spk;
Ulong ch_delay, dr_delay;
{
lprintf( stdout, " [event %s->%c @ %.1f] causes ",
pnode( cur_node ), vchars[ cur_node->npot ], d2ns( cur_delta ) );
if( dr_delay <= ch_delay )
lprintf( stdout, "suppressed " );
lprintf( stdout, "spike for %s: %c -> %c -> %c", pnode( nd ),
vchars[ nd->npot ], vchars[ spk->charge ], vchars[ nd->npot ] );
lprintf( stdout, " (peak=%.2f delay: ch=%.1fns, dr=%.1fns)\n",
spk->peak, d2ns( ch_delay ), d2ns( dr_delay ) );
}
private void print_spk( nd, r, tab, dom, alpha, beta, spk, is_spk )
nptr nd;
Thev r;
int tab, dom, alpha, beta;
pspk spk;
int is_spk;
{
char *net_type;
lprintf( stdout, " spike_analysis( %s ):", pnode( nd ) );
if( tab == LINEARSPK )
net_type = "n-p mix";
else if( tab == NLSPKMIN )
net_type = (dom == LOW) ? "nmos" : "pmos";
else
net_type = (dom == LOW) ? "pmos" : "nmos";
lprintf( stdout, " %s driven %s\n",
net_type, (dom == LOW) ? "low" : "high" );
lprintf( stdout, "{tauA=%.1f tauD=%.1f tauP=%.1f} ns ",
ps2ns( r->tauA ), ps2ns( r->tauD ), ps2ns( r->tauP ) );
lprintf( stdout, "alpha=%d beta=%d => peak=%.2f",
alpha, beta, spk->peak );
if( is_spk )
lprintf( stdout, " v=%c\n", vchars[ spk->charge ] );
else
lprintf( stdout, " (too small)\n" );
}